package net.joint_painting.client.model;

import gwt.g2d.client.graphics.Color;
import gwt.g2d.client.graphics.KnownColor;
import gwt.g2d.client.graphics.LineCap;
import gwt.g2d.client.graphics.LineJoin;
import gwt.g2d.client.graphics.Surface;
import gwt.g2d.client.graphics.shapes.ShapeBuilder;
import gwt.g2d.client.math.Vector2;

import java.util.HashMap;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;

import net.joint_painting.client.controller.NetworkController;
import net.joint_painting.client.controller.TimeController;
import net.joint_painting.client.controller.ViewController;
import net.joint_painting.client.observer.ModelEvent;
import net.joint_painting.client.observer.ModelListener;
import net.joint_painting.shared.model.Line;

import com.google.gwt.dom.client.ImageElement;
import com.google.gwt.user.client.DOM;
import com.google.gwt.user.client.Timer;

/**
 * 
 * @author CAM
 * @version $Revision: 1.0 $
 */
public class Model {

    private String id;

    private List<ModelListener> listeners = new LinkedList<ModelListener>();

    private Map<String, List<Line>> lines = new HashMap<String, List<Line>>();

    private Map<String, Surface> surfaces = new HashMap<String, Surface>();

    private TimeController timeController;
    private NetworkController networkController;
    private ViewController viewController;

    private Integer painterSize = 5;
    private Color painterColor = KnownColor.BLACK;
    private Integer zoomLevel = 1;
    private Integer positionX = 500;
    private Integer positionY = 500;

    /**
     * Constructor for Model.
     * 
     * @param id
     *            String
     * @throws Exception
     */
    public Model(String id) throws Exception {
	if (id == null)
	    throw new Exception("No ID");
	this.id = id;
	networkController = new NetworkController();
	timeController = new TimeController();
	viewController = new ViewController();
	networkController.setModel(this);
	viewController.setModel(this);
	timeController.attach(networkController);
	timeController.run();
    }

    /**
     * Method addSurface.
     * 
     * @param id
     *            String
     * @param data
     *            String
     */
    public void addSurface(final String id, String data) {
	final Surface surface = new Surface(
		net.joint_painting.shared.logic.Surface.size,
		net.joint_painting.shared.logic.Surface.size);
	final ImageElement e = DOM.createImg().cast();
	e.setSrc(data);
	Timer timer = new Timer() {

	    @Override
	    public void run() {
		surface.drawImage(e, 0, 0,
			net.joint_painting.shared.logic.Surface.size,
			net.joint_painting.shared.logic.Surface.size);
		surfaces.put(id, surface);
		Model.this
			.notify(new ModelEvent(this,
				ModelEvent.ModelEventType.UPDATE,
				net.joint_painting.shared.logic.Surface
					.getXFromID(id),
				net.joint_painting.shared.logic.Surface
					.getYFromID(id)));
	    }

	};
	timer.schedule(500);

    }

    /**
     * Method attach.
     * 
     * @param listener
     *            ModelListener
     */
    public void attach(ModelListener listener) {
	listeners.add(listener);
    }

    /**
     * Method detach.
     * 
     * @param listener
     *            ModelListener
     */
    public void detach(ModelListener listener) {
	listeners.remove(listener);
    }

    /**
     * Method drawLine.
     * 
     * @param line
     *            Line
     */
    public void drawLine(Line line) {
	Integer minX = Math.min(line.getX1(), line.getX2());
	Integer maxX = Math.max(line.getX1(), line.getX2());
	Integer minY = Math.min(line.getY1(), line.getY2());
	Integer maxY = Math.max(line.getY1(), line.getY2());

	for (int x = minX; x <= maxX
		+ net.joint_painting.shared.logic.Surface.size; x += net.joint_painting.shared.logic.Surface.size) {
	    for (int y = minY; y <= maxY
		    + net.joint_painting.shared.logic.Surface.size; y += net.joint_painting.shared.logic.Surface.size) {
		String id = net.joint_painting.shared.logic.Surface
			.getSurfaceID(this.id, x, y);
		Line l = new Line(id,
			line.getX1()
				- net.joint_painting.shared.logic.Surface
					.getSurfacePositionX(x),
			line.getY1()
				- net.joint_painting.shared.logic.Surface
					.getSurfacePositionY(y),
			line.getX2()
				- net.joint_painting.shared.logic.Surface
					.getSurfacePositionX(x),
			line.getY2()
				- net.joint_painting.shared.logic.Surface
					.getSurfacePositionY(y),
			line.getSize(), line.getColor());
		drawLine(id, l);
		networkController.drawLine(l);
		notify(new ModelEvent(this, ModelEvent.ModelEventType.UPDATE,
			x, y));
	    }
	}
    }

    /**
     * Method drawLine.
     * 
     * @param line
     *            Line
     * @param surface
     *            Surface
     */
    private void drawLine(Line line, Surface surface) {
	surface.setStrokeStyle(line.getColor()).setLineWidth(line.getSize());
	surface.save().setLineCap(LineCap.ROUND).setLineJoin(LineJoin.ROUND);
	surface.strokeShape(new ShapeBuilder().drawLineSegment(
		new Vector2(line.getX1(), line.getY1()),
		new Vector2(line.getX2(), line.getY2())).build());
	surface.restore();
    }

    /**
     * Method drawLine.
     * 
     * @param mapKey
     *            String
     * @param line
     *            Line
     */
    private void drawLine(String mapKey, Line line) {
	Integer size = net.joint_painting.shared.logic.Surface.size;
	Surface surface = surfaces.get(mapKey);
	List<Line> lines = this.lines.get(mapKey);
	if (surface == null) {
	    surface = new Surface(size, size);
	}
	if (lines == null)
	    lines = new LinkedList<Line>();
	lines.add(line);
	for (int i = 0; i < lines.size() - 1000; i++) {
	    lines.remove(i);
	}
	this.lines.put(mapKey, lines);
	drawLine(line, surface);
	surfaces.put(mapKey, surface);
    }

    /**
     * Method getSurfaceId.
     * 
     * @return String
     */
    public String getId() {
	return id;
    }

    /**
     * Method getLines.
     * 
     * @param areaX
     *            Integer
     * @param areaY
     *            Integer
     * @return List<Line>
     */
    public List<Line> getLines(Integer areaX, Integer areaY) {
	return getLines(net.joint_painting.shared.logic.Surface.getSurfaceID(
		this.id, areaX, areaY));
    }

    /**
     * Method getLines.
     * 
     * @param id
     *            String
     * @return List<Line>
     */
    public List<Line> getLines(String id) {
	List<Line> lines = this.lines.get(id);
	if (lines == null)
	    lines = new LinkedList<Line>();
	return lines;
    }

    /**
     * 
     * @return the painterColor
     */
    public Color getPainterColor() {
	return painterColor;
    }

    /**
     * 
     * @return the painterSize
     */
    public Integer getPainterSize() {
	return painterSize;
    }

    /**
     * Method getPositionX.
     * 
     * @return Integer
     */
    public Integer getPositionX() {
	return positionX;
    }

    /**
     * Method getPositionY.
     * 
     * @return Integer
     */
    public Integer getPositionY() {
	return positionY;
    }

    /**
     * Method getSurface.
     * 
     * @param areaX
     *            Integer
     * @param areaY
     *            Integer
     * @return Surface
     */
    public Surface getSurface(Integer areaX, Integer areaY) {
	String mapKey = net.joint_painting.shared.logic.Surface.getSurfaceID(
		id, areaX, areaY);
	Surface surface = surfaces.get(mapKey);
	if (surface == null) {
	    networkController.getSurfaceArea(areaX, areaY);
	}
	return surface;
    }

    /**
     * Method getTimeController.
     * 
     * @return TimeController
     */
    public TimeController getTimeController() {
	return timeController;
    }

    /**
     * Method getViewController.
     * 
     * @return ViewController
     */
    public ViewController getViewController() {
	return viewController;
    }

    /**
     * Method getZoomLevel.
     * 
     * @return Integer
     */
    public Integer getZoomLevel() {
	return zoomLevel;
    }

    public void moveDown() {
	positionY += zoomLevel
		* (net.joint_painting.shared.logic.Surface.size + zoomLevel);
	notify(new ModelEvent(this, ModelEvent.ModelEventType.MOVE));
    }

    public void moveLeft() {
	positionX -= zoomLevel
		* (net.joint_painting.shared.logic.Surface.size + zoomLevel);
	if (positionX < 0)
	    positionX = 0;
	notify(new ModelEvent(this, ModelEvent.ModelEventType.MOVE));
    }

    public void moveRight() {
	positionX += zoomLevel
		* (net.joint_painting.shared.logic.Surface.size + zoomLevel);
	notify(new ModelEvent(this, ModelEvent.ModelEventType.MOVE));
    }

    public void moveUp() {
	positionY -= zoomLevel
		* (net.joint_painting.shared.logic.Surface.size + zoomLevel);
	if (positionY < 0)
	    positionY = 0;
	notify(new ModelEvent(this, ModelEvent.ModelEventType.MOVE));
    }

    /**
     * Method notify.
     * 
     * @param event
     *            ModelEvent
     */
    public void notify(ModelEvent event) {
	for (int i = 0; i < listeners.size(); i++) {
	    listeners.get(i).onModelUpdate(event);
	}
    }

    /**
     * @param painterColor
     *            the painterColor to set
     */
    public void setPainterColor(Color painterColor) {
	this.painterColor = painterColor;
    }

    /**
     * @param painterSize
     *            the painterSize to set
     */
    public void setPainterSize(Integer painterSize) {
	this.painterSize = painterSize;
    }

    public void zoomIn() {/*
			   * switch (zoomLevel) { case 1: zoomLevel = 1; break;
			   * case 3: zoomLevel = 1; break; case 9: zoomLevel =
			   * 3; break; default: zoomLevel = 1; break; }
			   * notify(new ModelEvent(this,
			   * ModelEvent.ModelEventType.ZOOM));
			   */
    }

    public void zoomOut() {/*
			    * switch (zoomLevel) { case 1: zoomLevel = 3; break;
			    * case 3: zoomLevel = 9; break; case 9: zoomLevel =
			    * 9; break; default: zoomLevel = 1; break; }
			    * notify(new ModelEvent(this,
			    * ModelEvent.ModelEventType.ZOOM));
			    */
    }
}
